package codeGeneration.persistent;

import java.util.List;
import java.util.Properties;

import lexer.TokenConstant;
import util.ClassInformation;
import util.Common;
import util.Configuration;
import util.FileUtil;
import util.InformationForGenerate;
import util.Relationship;
import util.Tag;
import databaseTransaction.Command;
import databaseTransaction.Table;
import databaseTransaction.TableField;

public class HibernatePersistentGenerator extends PersistentGenerator {
	private String basePackageName;
	private Configuration config;
	private InformationForGenerate informationForGenerate;

	public HibernatePersistentGenerator(Table table, String basePath,
			String packageName, String basePackageName, Configuration config,
			InformationForGenerate informationForGenerate) {
		super(table, table.getName(), packageName);
		persistentFileLocation = basePath + '/' + packageName.replace('.', '/');
		daoFileLocation = basePath + '/' + daoPackageName.replace('.', '/');
		this.basePackageName = basePackageName;
		this.config = config;
		this.informationForGenerate = informationForGenerate;
		informationForGenerate.setPackageOfDAO(daoPackageName);

		FileUtil.mkDir(persistentFileLocation);
		FileUtil.mkDir(daoFileLocation);

		loadDefaultSessionFactory();
	}

	private void loadDefaultSessionFactory() {
		ClassInformation sessionFactoryClassInformation = config
				.checkWithIDOfBean("sessionFactory");

		if (sessionFactoryClassInformation.getClassName() == null
				|| "".equals(sessionFactoryClassInformation.getClassName())) {
			sessionFactoryClassInformation
					.setClassName("org.springframework.orm.hibernate3."
							+ "annotation.AnnotationSessionFactoryBean");

			Tag propertyTag = new Tag();
			propertyTag.setType("beans:property");

			Properties prop = new Properties();
			prop.setProperty("ref", "dataSource");
			prop.setProperty("name", "dataSource");
			propertyTag.setProp(prop);
			sessionFactoryClassInformation.addChildTag(propertyTag);

			propertyTag = new Tag();
			propertyTag.setType("beans:property");

			prop = new Properties();
			prop.setProperty("value", informationForGenerate.getPackageOfBean());
			prop.setProperty("name", "packagesToScan");
			propertyTag.setProp(prop);
			sessionFactoryClassInformation.addChildTag(propertyTag);

			// Add property tag which has a lot of child tag
			propertyTag = new Tag();
			propertyTag.setType("beans:property");
			prop = new Properties();
			prop.setProperty("name", "hibernateProperties");
			propertyTag.setProp(prop);

			// Child tag also has child tag, too

			Tag beansPropsTag = new Tag();
			beansPropsTag.setType("beans:props");

			Tag beansPropTag = new Tag();
			beansPropTag.setType("beans:prop");
			prop = new Properties();
			prop.setProperty("key", "hibernate.dialect");
			beansPropTag.setProp(prop);
			beansPropTag.setContent("org.hibernate.dialect.MySQLDialect");
			beansPropsTag.addChildTag(beansPropTag);

			beansPropTag = new Tag();
			beansPropTag.setType("beans:prop");
			prop = new Properties();
			prop.setProperty("key", "show_sql");
			beansPropTag.setProp(prop);
			beansPropTag.setContent("true");
			beansPropsTag.addChildTag(beansPropTag);

			// Add child tag to property tag
			propertyTag.addChildTag(beansPropsTag);
			sessionFactoryClassInformation.addChildTag(propertyTag);
		}
	}

	@Override
	protected void generateDeclare() {
		persistentHeaderOfJavaFile.setPackageName(persistentPackageName);
		persistentHeaderOfJavaFile
				.addImportsList("org.hibernate.SessionFactory");
		persistentHeaderOfJavaFile.addImportsList("org.springframework.beans."
				+ "factory.annotation.Autowired");
		persistentHeaderOfJavaFile.addImportsList("org.springframework.orm."
				+ "hibernate3.HibernateTemplate");
		persistentHeaderOfJavaFile.addImportsList("java.util.*");
		persistentHeaderOfJavaFile.addImportsList("org.springframework."
				+ "stereotype.Repository");
		persistentHeaderOfJavaFile.addImportsList(basePackageName + ".bean."
				+ className);
		persistentHeaderOfJavaFile.addImportsList(daoPackageName + '.'
				+ className + "DAO ");

		// Header of dao
		daoHeaderOfJavaFile.setPackageName(daoPackageName);
		daoHeaderOfJavaFile.addImportsList("java.util.*");
		daoHeaderOfJavaFile.addImportsList(basePackageName + ".bean."
				+ className);

		appendToFile(persistentBuffWriter, "@Repository\n");
		appendToFile(persistentBuffWriter, "public class " + className
				+ "Persistent implements " + className + "DAO {\n");
		indentOneTime("public interface " + className + "DAO ", null, "{",
				daoBuffWriter, false);
		appendToFile(persistentBuffWriter,
				"private HibernateTemplate hibernateTemplate;\n");

		informationForGenerate.setNameOfDAOClass(className + "DAO");
		generateAutowireSessionFactory();
	}

	private void generateAutowireSessionFactory() {
		appendToFile(persistentBuffWriter, "\n");
		appendToFile(persistentBuffWriter, "@Autowired\n");
		indentOneTime("public void setSessionFactory"
				+ "(SessionFactory sessionFactory)", null, " {",
				persistentBuffWriter, false);

		appendToFile(persistentBuffWriter,
				"hibernateTemplate = new HibernateTemplate(sessionFactory);\n");
		decreaseIndentOneTime("}", persistentBuffWriter);
	}

	@Override
	protected void generateGetAll() {
		appendToFile(persistentBuffWriter, "\n");
		String methodName = "getAll" + className + "()";
		indentOneTime("public List<" + this.className + "> " + methodName,
				null, " {", persistentBuffWriter, false);

		appendToFile(persistentBuffWriter, "List<" + this.className + "> "
				+ defaultObjName + 's' + ";\n");
		appendToFile(persistentBuffWriter, defaultObjName
				+ "s = hibernateTemplate.find(\"from " + this.className
				+ "\");\n");
		appendToFile(persistentBuffWriter, "return " + defaultObjName + 's'
				+ ";\n");
		decreaseIndentOneTime("}", persistentBuffWriter);

		// append to dao
		appendToFile(daoBuffWriter, "List<" + this.className + "> "
				+ methodName + ";\n");
	}

	@Override
	protected void generateGetById() {
		appendToFile(persistentBuffWriter, "\n");
		String primaryField = "";
		String primaryType = "";
		String id = "";

		String functionDeclare = "public " + className + " get" + className
				+ "ById(";
		String query = "from " + className + " where ";
		String args = "";
		for (TableField idField : this.getTable().getId()) {
			primaryField = idField.getName();
			primaryType = idField.parseType();

			id = id + primaryType + " " + primaryField + ", ";
			query = query + primaryField + " = ?, ";
			args = primaryField + ", ";
		}
		id = id.substring(0, id.length() - 2);
		functionDeclare = functionDeclare + id + ")";
		query = query.substring(0, query.length() - 2);
		args = args.substring(0, args.length() - 2);

		indentOneTime(functionDeclare, null, " {", persistentBuffWriter, false);

		appendToFile(persistentBuffWriter, " return (" + className
				+ ") hibernateTemplate.find(\"" + query + "\"" + ", " + args
				+ ").get(0);\n");
		decreaseIndentOneTime("}", persistentBuffWriter);

		// DAO
		appendToFile(
				daoBuffWriter,
				functionDeclare.substring(functionDeclare.indexOf(' ') + 1,
						functionDeclare.length()) + ";\n");
	}

	@Override
	protected void generateGetAndSetBy() {
		String type = "";
		String name = "";

		List<Command> commands = table.getCommands();

		for (Command command : commands) {
			TableField fieldName = command.getFieldName();
			List<TableField> listOfWhereFieldName = command
					.getListOfWhereFieldName();

			if (fieldName != null) {
				type = fieldName.parseType();
				name = fieldName.getName();

				if (type.equals("int"))
					type = "Integer";

				Relationship relationship = fieldName.getRelationship();
				if (relationship != null) {
					type = getTypeWithRelationship(relationship);
					name = getNameWithRelationship(relationship);
				}

				String upperCaseFieldName = Common
						.convertFirstLetterToUpperCase(name);

				boolean isPK = true;
				StringBuffer nameOfMethod = new StringBuffer();
				StringBuffer args = new StringBuffer();
				String returnValue = "";

				if (command.getTypeOfCommand() == TokenConstant.GET)
					nameOfMethod.append("get" + upperCaseFieldName + "By");

				if (command.getTypeOfCommand() == TokenConstant.SET)
					nameOfMethod.append("update" + upperCaseFieldName + "By");

				for (int i = 0; i < listOfWhereFieldName.size(); ++i) {
					TableField tableField = listOfWhereFieldName.get(i);
					if (isPK
							&& !tableField.getExtra()
									.contains(TokenConstant.PK))
						isPK = false;

					args.append(tableField.parseType());
					args.append(' ');
					args.append(tableField.getName());

					String whereFieldName = tableField.getName();
					whereFieldName = Common
							.convertFirstLetterToUpperCase(whereFieldName);

					nameOfMethod.append(whereFieldName);
					if (i < listOfWhereFieldName.size() - 1) {
						nameOfMethod.append("And");
						args.append(", ");
					}
				}

				if (command.getTypeOfCommand() == TokenConstant.GET) {
					if (isPK) {
						if (relationship.getRelationship() == TokenConstant.MANYTOMANY
								|| relationship.getRelationship() == TokenConstant.ONETOMANY)
							returnValue = "Set<" + type + '>';
						else
							returnValue = type;
					} else
						returnValue = "List<" + type + '>';
				} else if (command.getTypeOfCommand() == TokenConstant.SET) {
					returnValue = "";
					args.append(", ");
					args.append(type);
					args.append(' ');
					args.append(name);
				}

				appendToFile(persistentBuffWriter, "\n");

				String completeMethodName = returnValue + ' ' + nameOfMethod
						+ '(' + args + ')';
				appendToFile(daoBuffWriter, completeMethodName + ";\n");
				indentOneTime("public " + completeMethodName.toString(), null,
						" {", persistentBuffWriter, false);

				if (command.getTypeOfCommand() == TokenConstant.GET)
					generateGetBy(fieldName, listOfWhereFieldName);
				if (command.getTypeOfCommand() == TokenConstant.SET)
					generateSetBy(fieldName, listOfWhereFieldName);
			}
		}
	}

	private void generateSetBy(TableField fieldName,
			List<TableField> listOfWhereFieldName) {
		appendToFile(persistentBuffWriter, "List<" + this.className + "> "
				+ defaultObjName + 's' + ";\n");

		StringBuffer query = new StringBuffer();
		query.append("\"from " + className + " where ");

		StringBuffer args = new StringBuffer();

		String name = fieldName.getName();
		Relationship relationship = fieldName.getRelationship();
		if (relationship != null) {
			name = getNameWithRelationship(relationship);
		}

		for (int i = 0; i < listOfWhereFieldName.size(); ++i) {
			TableField tableField = listOfWhereFieldName.get(i);
			query.append(tableField.getName() + " = ?\"");
			args.append(tableField.getName());

			if (i < listOfWhereFieldName.size() - 1) {
				query.append(" and ");
				args.append(", ");
			}
		}

		appendToFile(persistentBuffWriter, defaultObjName
				+ "s = hibernateTemplate.find(" + query.toString() + ", "
				+ args.toString() + ");\n");
		String objName = Common.convertFirstLetterToLowerCase(className);
		indentOneTime("for(" + className + ' ' + objName + " : " + objName
				+ "s) ", null, "", persistentBuffWriter, false);

		appendToFile(persistentBuffWriter,
				objName + ".set" + Common.convertFirstLetterToUpperCase(name)
						+ '(' + name + ");\n");
		decreaseIndentOneTime("", persistentBuffWriter);

		indentOneTime("for(" + className + ' ' + objName + " : " + objName
				+ "s) ", null, "", persistentBuffWriter, false);

		appendToFile(persistentBuffWriter, "hibernateTemplate.update("
				+ objName + ");\n");

		decreaseIndentOneTime("", persistentBuffWriter);
		decreaseIndentOneTime("}", persistentBuffWriter);
	}

	private void generateGetBy(TableField fieldName,
			List<TableField> listOfWhereFieldName) {
		StringBuffer query = new StringBuffer();
		String type = fieldName.parseType();
		String name = fieldName.getName();
		StringBuffer args = new StringBuffer();
		String objName = Common.convertFirstLetterToLowerCase(className);

		if (type.equals("int"))
			type = "Integer";
		if (type.equals("boolean"))
			type = "Boolean";

		Relationship relationship = fieldName.getRelationship();
		if (relationship != null) {
			type = getTypeWithRelationship(relationship);
			name = getNameWithRelationship(relationship);

			persistentHeaderOfJavaFile.addImportsList(basePackageName
					+ ".bean." + Common.convertFirstLetterToUpperCase(name));
			daoHeaderOfJavaFile.addImportsList(basePackageName + ".bean."
					+ Common.convertFirstLetterToUpperCase(name));
		}

		query.append("\"from " + table.getName() + " where ");
		boolean isPK = true;
		for (int i = 0; i < listOfWhereFieldName.size(); ++i) {
			TableField tableField = listOfWhereFieldName.get(i);
			if (isPK && !tableField.getExtra().contains(TokenConstant.PK))
				isPK = false;

			query.append(tableField.getName() + " = ?");
			args.append(tableField.getName());

			if (i < listOfWhereFieldName.size() - 1) {
				query.append(" and ");
				args.append(", ");
			}
		}
		query.append('\"');

		if (isPK) {
			appendToFile(persistentBuffWriter, className + ' ' + objName
					+ " = new " + className + "();\n");
			appendToFile(persistentBuffWriter, objName + " = (" + className
					+ ") hibernateTemplate.find(" + query.toString() + ", "
					+ args.toString() + ").get(0);\n");

			if (relationship.getRelationship() == TokenConstant.MANYTOMANY
					|| relationship.getRelationship() == TokenConstant.ONETOMANY)
				type += 's';

			appendToFile(persistentBuffWriter, "return " + objName + ".get"
					+ type + "();\n");

		} else {
			appendToFile(persistentBuffWriter, "List<" + className + "> "
					+ objName + "s = new ArrayList<" + className + ">();\n");
			appendToFile(persistentBuffWriter, objName
					+ "s = hibernateTemplate.find(" + query.toString() + ", "
					+ args.toString() + ");\n");

			appendToFile(persistentBuffWriter, "List<" + type + "> " + name
					+ "s = new ArrayList<" + type + ">();\n");

			indentOneTime("for(" + className + ' ' + objName + " : " + objName
					+ "s) ", null, "", persistentBuffWriter, false);

			appendToFile(persistentBuffWriter, name + "s.add(" + objName
					+ ".get" + Common.convertFirstLetterToUpperCase(name)
					+ "());\n");

			decreaseIndentOneTime("", persistentBuffWriter);
			appendToFile(persistentBuffWriter, "return " + name + "s;\n");
		}

		decreaseIndentOneTime("}", persistentBuffWriter);
	}

	@Override
	protected void generateAdd() {
		appendToFile(persistentBuffWriter, "\n");
		String classBeanName = table.getName();
		String objBeanName = Common
				.convertFirstLetterToLowerCase(classBeanName);

		String functionDeclare = "public void add" + className + "(";
		functionDeclare = functionDeclare + classBeanName + ' ' + objBeanName
				+ ')';

		indentOneTime(functionDeclare, null, "{", persistentBuffWriter, false);

		appendToFile(persistentBuffWriter, "hibernateTemplate.save("
				+ defaultObjName + ");\n");
		decreaseIndentOneTime("}", persistentBuffWriter);

		// Append dao
		appendToFile(
				daoBuffWriter,
				functionDeclare.substring(functionDeclare.indexOf(' ') + 1,
						functionDeclare.length()) + ";\n");
	}

	@Override
	protected void generateRemove() {
		appendToFile(persistentBuffWriter, "\n");
		String primaryField = "";
		String primaryType = "";
		String id = "";

		String functionDeclare = "public void remove" + className + "(";
		for (TableField idField : this.getTable().getId()) {
			primaryField = idField.getName();
			primaryType = idField.parseType();

			id = primaryField + ", ";
			functionDeclare = functionDeclare + primaryType + ' '
					+ primaryField + ", ";
		}

		functionDeclare = functionDeclare.substring(0,
				functionDeclare.length() - 2) + ")";
		id = id.substring(0, id.length() - 2);

		indentOneTime(functionDeclare, null, " {", persistentBuffWriter, false);

		appendToFile(persistentBuffWriter, "hibernateTemplate.delete(get"
				+ className + "ById(" + id + "));\n");
		decreaseIndentOneTime("}", persistentBuffWriter);

		// DAO
		appendToFile(
				daoBuffWriter,
				functionDeclare.substring(functionDeclare.indexOf(' ') + 1,
						functionDeclare.length()) + ";\n");
	}

	@Override
	protected void generateRemoveAll() {
		appendToFile(persistentBuffWriter, "\n");
		indentOneTime("public void removeAll()", null, " {",
				persistentBuffWriter, false);

		appendToFile(persistentBuffWriter,
				"hibernateTemplate.deleteAll(hibernateTemplate.find"
						+ "(\"from " + this.className + "\"));\n");
		decreaseIndentOneTime("}", persistentBuffWriter);

		// DAO
		appendToFile(daoBuffWriter, "void removeAll();\n");
	}

	private String getTypeWithRelationship(Relationship relationship) {
		String type = "";
		switch (relationship.getRelationship()) {
		case TokenConstant.MANYTOONE:
		case TokenConstant.ONETOMANY:
		case TokenConstant.MANYTOMANY:
			type = relationship.getTableName();
			break;
		}

		return type;
	}

	private String getNameWithRelationship(Relationship relationship) {
		String name = "";
		switch (relationship.getRelationship()) {
		case TokenConstant.MANYTOONE:
		case TokenConstant.ONETOMANY:
		case TokenConstant.MANYTOMANY:
			name = Common.convertFirstLetterToLowerCase(relationship
					.getTableName());
			break;
		}

		return name;
	}
}